/*******************************************************************
 *
 * Copyright (C), 2021-2022, LS, All rights reserved.
 *
 * Author      : Fengze Luo
 * Create Time : 2021-10-21
 * Version     : v0.1
 * Description : 
 *******************************************************************/

/*----------------------------Head file----------------------------*/
#include "string.h"
#include "slboot.h"
#include "slb_flash.h"
#include "ota_crc16.h"
#include "jump_function.h"
#include "watchdog.h"
#include "flash.h"
#include "log.h"

/*----------------------------macro file---------------------------*/
#define APP_BANK_PARTITION_OFFSET	16

#define SLB_FLASH_CACHE_ENTER_BYPASS_SECTION()  do{ \
    AP_CACHE->CTRL0 = 0x02; \
    AP_PCR->CACHE_RST = 0x02;\
    AP_PCR->CACHE_BYPASS = 1;    \
}while(0);


#define SLB_FLASH_CACHE_EXIT_BYPASS_SECTION()  do{ \
    AP_CACHE->CTRL0 = 0x00;\
    AP_PCR->CACHE_RST = 0x03;\
    AP_PCR->CACHE_BYPASS = 0;\
}while(0);

/*----------------------------type define--------------------------*/
typedef struct {
    uint32_t flashAddress;
    uint32_t runAddress;
    uint32_t partitionSize;
    uint32_t crc32Checksum;
} BootInfoPartition_t;

/*----------------------------var define---------------------------*/
const char*  tag_ota_fw = "OTAF";
uint8_t s_partition_buf[16*1024+16] __attribute__((section("ota_partition_buffer_area")));

/*-------------------------func declaration------------------------*/

/*-----------------------------------------------------------------*/

//Note: Because PHY62xx SDK structure and scatter load file, PHY62xx bin boot format need to be divied to mutiple partition.

/********** OTA file format *************************/
/*  1. OTA file header Info.
**  2. Partition num only one, content include jump_table and irom1 code.
**
*/

/********** PHY62xx bin format *************************/
/*  1. Jump table header info.
**  2. Partition No.1, content include jump_table code.
**
**  3. IROM1 header info.
**  4. Partition No.2, content include IROM1 code.
*/


int slb_spif_write(uint32_t faddr, uint8_t* pdata, uint32_t size)
{
    if(faddr & 3)
    {
        return PPlus_ERR_DATA_ALIGN;
    }

    return(hal_flash_write_by_dma(faddr, (uint8_t*) pdata, size));
}


int slb_spif_read(uint32_t faddr, uint8_t* pdata, uint32_t size)
{
    SLB_FLASH_CACHE_ENTER_BYPASS_SECTION();
    memcpy(pdata, (uint8_t*)faddr, size);
    SLB_FLASH_CACHE_EXIT_BYPASS_SECTION();
    return PPlus_SUCCESS;
}


int slb_spif_erase(uint32_t faddr, uint32_t size)
{
    uint32_t endaddr = faddr + size;
    faddr = faddr & 0xfffff000;

    while(faddr < endaddr)
    {
        hal_flash_erase_sector(faddr);
        faddr += 0x1000;//unit is 4K
    }

    return PPlus_SUCCESS;
}


__asm void __attribute__((section("ota_app_loader_area"))) jump2app(void)
{
    LDR R0, = __APP_RUN_ADDR__
              LDR R1, [R0, #4]
              BX R1
              ALIGN
}


void __attribute__((section("ota_app_loader_area"))) run_application(void)
{
    int ret;
    HAL_ENTER_CRITICAL_SECTION();
    ret = ota_flash_load_app();
    if(ret == PPlus_SUCCESS)
    {
        jump2app();
    }
    HAL_EXIT_CRITICAL_SECTION();
}


//mark the exchange zone as garbage data
#define SLB_DELETE_EXCHANGE_ZONE()    {uint32_t tmpdata = 0;slb_spif_write(SLB_FLASH_FW_BASE, (uint8_t*)(&tmpdata), 4);}

static void slb_erase_app_bank(void)
{
    //erase boot sector
    hal_flash_erase_sector(OTAF_2nd_BOOTINFO_ADDR);
    for(uint32_t i = 0; i< APP_IMG_MAX_SIZE; i+= OTAF_SECTOR_SIZE)
        hal_flash_erase_sector(APP_BANK_BIN_ADDR + i);
}



static int slb_apply_exch_zone_to_fw(uint32_t part_num)
{
    int i;
    uint32_t flash_offset = 0;
    uint32_t ram_part_idx = 0;
    BootInfoPartition_t part_info;

    //make exchange zone effect(load data to boot zone)
    slb_erase_app_bank();

    for(i = 0; i< part_num; i++)
    {
        slb_spif_read(SLB_FLASH_FW_PART_FADDR(i), (uint8_t*)&part_info, 0x10);
        slb_spif_read(SLB_FLASH_PART_DATA_BASE + part_info.flashAddress, (uint8_t*)s_partition_buf, part_info.partitionSize);
        //LOG("part_num:%d,fladdr:%x,size:%x,crc:%x \n",i,part_info.runAddress,part_info.partitionSize,part_info.crc32Checksum);
        if((part_info.runAddress & 0xff000000) == OTAF_BASE_ADDR)//case runaddr is in XIP
        {
            //erase
            slb_spif_erase(part_info.runAddress, part_info.partitionSize);
            slb_spif_write(part_info.runAddress, (uint8_t*)s_partition_buf, part_info.partitionSize);
        }
        else
        {
            LOG("flash_offset: %x.\n", flash_offset);
            slb_spif_write(APP_BANK_BIN_ADDR + flash_offset, (uint8_t*)s_partition_buf, part_info.partitionSize);
            if(i > 0) //prepare boot sector
            {
                part_info.flashAddress = flash_offset;
                slb_spif_write(OTAF_2nd_BOOTINFO_ADDR + 0x10 + 0x10*ram_part_idx, (uint8_t*)&part_info, 0x10);
                ram_part_idx ++;
            }

            flash_offset += part_info.partitionSize + 4;
            flash_offset &= 0xfffffffc;//word align
        }
    }

    //prepare boot
    part_info.flashAddress = ram_part_idx;
    part_info.runAddress = 0;//single bank
    part_info.partitionSize = 0;
    part_info.crc32Checksum = 0xffffffff;
    slb_spif_write(OTAF_2nd_BOOTINFO_ADDR, (uint8_t*)&part_info, 0x10);
    
    return PPlus_SUCCESS;
}



int slb_boot_check_ota_bin_valid(uint32_t *part_cnt)
{
    uint32_t part_num = 0;
    uint8_t read_tmp_data[32];
    BootInfoPartition_t part_info;

    memset(read_tmp_data, 0, sizeof(read_tmp_data));
    slb_spif_read(SLB_FLASH_FW_BASE, read_tmp_data, 4);
    if(strcmp((const char*)read_tmp_data, tag_ota_fw) != 0)
    {
        LOG("Tag err. %s\n", read_tmp_data);
        return PPlus_ERR_NOT_FOUND;
    }

    slb_spif_read(SLB_FLASH_FW_PART_NUM, (uint8_t*)(&part_num), 4);
    if(part_num < 2 || part_num> OTAF_PARTITION_NUM_MAX)
    {
        LOG("part_num err: %d.\n", part_num);
        return PPlus_ERR_INVALID_DATA;
    }

    //check partition 0, if run address is 0xffffxxxx, it is special partition, need not load
    slb_spif_read(SLB_FLASH_FW_PART_FADDR(0), (uint8_t*)&part_info, 0x10);
    if((part_info.runAddress & 0xffff0000) != 0xffff0000)
    {
        LOG("part info err: %x.\n", part_info.runAddress);
        return PPlus_ERR_INVALID_DATA;
    }
    
    //check ota partition validation
    for(int i = 1; i< part_num; i++)
    {
        uint32_t crc = 0;
        slb_spif_read(SLB_FLASH_FW_PART_FADDR(i), (uint8_t*)&part_info, 0x10);
        slb_spif_read(SLB_FLASH_PART_DATA_BASE + part_info.flashAddress, (uint8_t*)s_partition_buf, part_info.partitionSize);
        crc = crc16(0, (const volatile void*)s_partition_buf, part_info.partitionSize);
        if(crc != part_info.crc32Checksum) {
            return PPlus_ERR_INVALID_DATA;
        }
    }

    *part_cnt = part_num;
    return PPlus_SUCCESS;
}



int ota_flash_check_app_bank_valid(void)
{
    int i;
    uint32_t partition_num = 0;
    uint32_t bank_info = 0;
    uint32_t bank_addr = 0;
    
    BootInfoPartition_t part_info;
    uint32_t ota_load_crc = 0;
    uint32_t ota_boot_bypass_crc = 0;
    
    slb_spif_read(OTAF_2nd_BOOTINFO_ADDR, (uint8_t*)&partition_num, 4);
    if(partition_num == 0xffffffff)
    {
        return PPlus_ERR_OTA_NO_APP;
    }

    if(partition_num > OTAF_PARTITION_NUM_MAX || partition_num == 0)
    {
        return PPlus_ERR_OTA_BAD_DATA;
    }

    slb_spif_read(OTAF_2nd_BOOTINFO_ADDR + 4, (uint8_t*)&bank_info, 4);
    if(bank_info == OTAF_DUAL_BANK_1)
    {
        bank_addr = OTA_MAGIC_CODE;
    }
    else if(bank_info == OTAF_DUAL_BANK_0 || bank_info == OTAF_SINGLE_BANK)
    {
        bank_addr = APP_BANK_BIN_ADDR;
    }
    else
    {
        return PPlus_ERR_OTA_BAD_DATA;
    }

    //Note: Can not add log below..., or it will make system crash.
    volatile uint32_t t = 100;
    while(t--) {};

    slb_spif_read(OTA_FAST_BOOT_MAGIC, (uint8_t*)&ota_boot_bypass_crc, 4);
    for(i = 1; i< partition_num+1; i++)
    {
        slb_spif_read(OTAF_2nd_BOOTINFO_ADDR + i*4*4, (uint8_t*)&part_info, 0x10);
        LOG("flash_addr: %x.\n", part_info.flashAddress);
        LOG("run_addr: %x.\n", part_info.runAddress);
        LOG("size: %x.\n", part_info.partitionSize);
        LOG("checksum: %x.\n", part_info.crc32Checksum);
        
        if((part_info.flashAddress == 0xffffffff) || (part_info.runAddress == 0xffffffff ) || (part_info.partitionSize == 0xffffffff ) || (part_info.crc32Checksum == 0xffffffff))
        {
            return PPlus_ERR_OTA_NO_APP;
        }

        //case XIP mode, shoud be in single bank and no fct
        if(part_info.runAddress == part_info.flashAddress)
        {
            if(ota_boot_bypass_crc != OTA_FAST_BOOT_MAGIC)
            {
                ota_load_crc = crc16(0, (const volatile void* )part_info.flashAddress, part_info.partitionSize);
                if(ota_load_crc != part_info.crc32Checksum)
                {
                    //if crc incorrect, reboot to OTA mode
                    return PPlus_ERR_OTA_CRC;
                }
            }
        }

        if((part_info.runAddress & 0xffff0000) == 0xffff0000)
        {
            continue;
        }

        //load binary
        //LOG("load binary 1111: %x, %x, %x.\n", bank_addr, part_info.flashAddress, part_info.partitionSize);
        slb_spif_read(part_info.flashAddress + bank_addr, s_partition_buf, part_info.partitionSize);
        if(ota_boot_bypass_crc != OTA_FAST_BOOT_MAGIC)
        {
            ota_load_crc = crc16(0, (const volatile void*)s_partition_buf, part_info.partitionSize);
            if(ota_load_crc != part_info.crc32Checksum)
            {
                LOG("crc err: %x.\n", ota_load_crc);
                //if crc incorrect, reboot to OTA mode
                return PPlus_ERR_OTA_CRC;
            }
        }
    }
    
    return PPlus_SUCCESS;
}



void boot_app(void)
{
    int ret = PPlus_SUCCESS;
    uint32_t part_num = 0;
    uint8_t try_times = 0;
    
    for(try_times=0;try_times<2;try_times++) {
        ret = slb_boot_check_ota_bin_valid(&part_num);
        if(PPlus_SUCCESS == ret) {
            break;
        }
        else {
            LOG("ota bin unvalid.\n");
        }
    }

    if(PPlus_SUCCESS == ret) {
        //if the fw information of exhcange zone(version, device type,hw version, boot address) is valid
        for(try_times=0;try_times<2;try_times++) {
            slb_apply_exch_zone_to_fw(part_num);
            ret = ota_flash_check_app_bank_valid();
            if(PPlus_SUCCESS == ret) {
                SLB_DELETE_EXCHANGE_ZONE();
                break;
            }
            else {      //If fail we try again.
                LOG("ota_flash_check_app_bank_valid fail: %d.\n", ret);
            }
        }
    }
    else if(PPlus_ERR_INVALID_DATA == ret){
        SLB_DELETE_EXCHANGE_ZONE();
    }
    
    LOG("ret: %d.\n", ret);
    run_application();
    while(1)
    {
        ;
    }
}

